﻿/*
#-------------------------------------------------------------------------------
# Name:        OCR - dual way pixel count normalized
#
# Usage:       Load an image of a character (black character on white background).
#              Pressing the learn button will add the character to learned.txt
#              The OCR button will start the character recognition procedure during
#              wich the loaded image will be compared to learned characters.
# 
# Requires:    Visual Studio 2010 and up.
#
# Author:      Tjerk Slaghek
#
# Created:     09-03-2012
# Copyright:   (c) Tjerk 2012
# Licence:     WTFPL
#-------------------------------------------------------------------------------
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace kentekenHerkenning
{
    public partial class Form1 : Form
    {   
        //a few variables that need to be public
        Image inputImage, outputImage;
        float[] normalizedColumns = new float[20];
        float[] normalizedRows = new float[20];

        public Form1()
        {
            InitializeComponent();
        }
        private void load_Click(object sender, EventArgs e)
        {
            
            OpenFileDialog openFileDialog1 = new OpenFileDialog();
            openFileDialog1.Filter = "All files (*.*)|*.*" ;
            openFileDialog1.FilterIndex = 2 ;
            openFileDialog1.RestoreDirectory = true ;

            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            { 
                inputImage = Image.FromFile(openFileDialog1.FileName);
                inputpicturebox.Image = inputImage;
            }

            outputpicturebox.Image = outputImage = inputImage;

            if (inputImage != null)
            {
                Bitmap inputBitmap = (Bitmap)inputImage;

                //the image dimensions must be divisible by the number of bins used in the normalisation
                Bitmap outputBitmap = cropAndResize(inputBitmap, 400, 400);
                outputpicturebox.Image = outputBitmap;

                int threshold = 0;
                Color tempColor;
                try { threshold = int.Parse(thresholdTextBox.Text); }
                catch (FormatException) { }
                //count black pixels per row from top to bottom
                int[] rows = new int[outputBitmap.Height];
                for (int i = 1; (i < outputBitmap.Height); i++)
                {
                    int row = 0;
                    for (int ii = 1; (ii < outputBitmap.Width); ii++)
                    {
                        tempColor = outputBitmap.GetPixel(i, ii);
                        if (((tempColor.R == tempColor.G) && (tempColor.G == tempColor.B)) && (tempColor.R + tempColor.G + tempColor.B) <= threshold)
                        {
                            row++;
                        }
                    }
                    rows[i - 1] = row;
                }
                //count black pixels per column from left to right
                int[] columns = new int[outputBitmap.Width];
                for (int i = 1; (i < outputBitmap.Width); i++)
                {
                    int column = 0;
                    for (int ii = 1; (ii < outputBitmap.Height); ii++)
                    {
                        tempColor = outputBitmap.GetPixel(ii, i);
                        if (((tempColor.R == tempColor.G) && (tempColor.G == tempColor.B)) && (tempColor.R + tempColor.G + tempColor.B) <= threshold)
                        {
                            outputBitmap.SetPixel(ii, i, Color.Red);
                            column++;
                        }
                    }
                    columns[i - 1] = column;
                }

                //NORMALISATION
                normalizedRows = normalizeIntArrayToFloats(rows, 20);
                normalizedColumns = normalizeIntArrayToFloats(columns, 20);

                /////////////ADD TO CHART IN GUI (please don't watch, horrible code)
                System.Windows.Forms.DataVisualization.Charting.Series series = new System.Windows.Forms.DataVisualization.Charting.Series("rows");
                int counter = 0;
                foreach (int binelement in rows)
                {
                    series.Points.Add(new System.Windows.Forms.DataVisualization.Charting.DataPoint(counter, binelement));
                    counter = counter + 1;
                }
                directChart.Series.Remove(directChart.Series.FindByName("Series1"));
                directChart.Series.Remove(directChart.Series.FindByName("rows"));
                directChart.Series.Add(series);
                System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series("columns");
                counter = 0;
                foreach (int binelement in columns)
                {
                    series1.Points.Add(new System.Windows.Forms.DataVisualization.Charting.DataPoint(counter, binelement));
                    counter = counter + 1;
                }
                directChart.Series.Remove(directChart.Series.FindByName("columns"));
                directChart.Series.Add(series1);
                System.Windows.Forms.DataVisualization.Charting.Series normalizedColumnsSeries = new System.Windows.Forms.DataVisualization.Charting.Series("columns");
                System.Windows.Forms.DataVisualization.Charting.Series normalizedRowsSeries = new System.Windows.Forms.DataVisualization.Charting.Series("rows");
                counter = 0;
                foreach (float binelement in normalizedRows)
                {
                    normalizedRowsSeries.Points.Add(new System.Windows.Forms.DataVisualization.Charting.DataPoint(counter, binelement));
                    counter = counter + 1;
                }
                counter = 0;
                foreach (float binelement in normalizedColumns)
                {
                    normalizedColumnsSeries.Points.Add(new System.Windows.Forms.DataVisualization.Charting.DataPoint(counter, binelement));
                    counter = counter + 1;
                }
                normalizedChart.Series.Remove(normalizedChart.Series.FindByName("Series1"));
                normalizedChart.Series.Remove(normalizedChart.Series.FindByName("columns"));
                normalizedChart.Series.Remove(normalizedChart.Series.FindByName("rows"));
                normalizedChart.Series.Add(normalizedRowsSeries);
                normalizedChart.Series.Add(normalizedColumnsSeries);
            }
        }
        /// <summary>
        /// converts an integer array to a normalized array of floats
        /// </summary>
        /// <param name="intArray">integer array containing the integers to be normalized</param>
        /// <param name="bins">amount of bins for the normalized integers</param>
        /// <returns></returns>
        private float[] normalizeIntArrayToFloats(int[] intArray, int bins)
        {
            float[] normalized = new float[intArray.Length];
            int intArraySum = 0;

            foreach (int i in intArray)
            {
                intArraySum += i;
            }
            for (int i = 0; i < intArray.Length; i++)
            {
                normalized[i] = ((float)intArray[i]/(float)intArraySum);
            }

            float[] normalizedBins = new float[bins];
            int step = intArray.Length / bins;
            int stepper = 0;
            for (int i = 0; i < bins; i++)
            {
                for (int ii = 0; ii < step; ii++)
                {
                    normalizedBins[i] += normalized[stepper++];
                }
            }
            return normalizedBins;
        }
        /// <summary>
        /// crops away the white edges closing in the sides until a black pixel is found
        /// rezises the bitmap to the given width and height 
        /// </summary>
        /// <param name="inputBitmap"></param>
        /// <param name="newWidth"></param>
        /// <param name="newHeight"></param>
        /// <returns>rezized cropped bitmap</returns>
        private Bitmap cropAndResize(Bitmap inputBitmap, int newWidth, int newHeight)
        {   
            //from top to bottom
            Boolean stop = false;
            int top = 0;
            for (int i = 1; (i < inputBitmap.Height) && !stop; i++)
            {

                for (int ii = 1; (ii < inputBitmap.Width); ii++)
                {
                    if (inputBitmap.GetPixel(ii, i) == Color.FromArgb(255, 0, 0, 0))
                    {
                        top = i;
                        stop = true;
                        break;
                    }
                }
            }

            //from bottom to top
            stop = false;
            int bottom = 0;
            for (int i = inputBitmap.Height - 1; (i > 0) && !stop; i--)
            {
                for (int ii = 1; (ii < inputBitmap.Width); ii++)
                {
                    if (inputBitmap.GetPixel(ii, i) == Color.FromArgb(255, 0, 0, 0))
                    {
                        bottom = i;
                        stop = true;
                        break;
                    }
                }
            }

            //from right to left
            stop = false;
            int right = 0;
            for (int i = inputBitmap.Width - 1; (i > 0) && !stop; i--)
            {
                for (int ii = 1; (ii < inputBitmap.Height); ii++)
                {
                    if (inputBitmap.GetPixel(i, ii) == Color.FromArgb(255, 0, 0, 0))
                    {
                        right = i;
                        stop = true;
                        break;
                    }
                }
            }

            //from left to right
            stop = false;
            int left = 0;
            for (int i = 1; (i < inputBitmap.Width) && !stop; i++)
            {
                for (int ii = 1; (ii < inputBitmap.Height); ii++)
                {
                    if (inputBitmap.GetPixel(i, ii) == Color.FromArgb(255, 0, 0, 0))
                    {
                        left = i;
                        stop = true;
                        break;
                    }
                }
            }

            Rectangle rect = new Rectangle(left, top, inputBitmap.Width - (left + (inputBitmap.Width - right)), inputBitmap.Height - (top + (inputBitmap.Height - bottom)));
            Bitmap temp = inputBitmap.Clone(rect, inputBitmap.PixelFormat);
            
            Bitmap outputBitmap = new Bitmap(newWidth, newHeight);
            using (Graphics g = Graphics.FromImage((Image)outputBitmap)) g.DrawImage(temp, 0, 0, newWidth, newHeight);
            return outputBitmap;
        }



        private void learnButton_Click(object sender, EventArgs e)
        {
            if ((textBox1.Text.Length == 1) && (inputImage != null))
            {
                System.IO.StreamWriter learningFile = new System.IO.StreamWriter(@"learned.txt", true);
                learningFile.Write(textBox1.Text + ";");
                foreach (float f in normalizedColumns)
                {
                    learningFile.Write(f + ":");
                }
                foreach (float f in normalizedRows)
                {
                    learningFile.Write(f + ":");
                }
                learningFile.WriteLine("");
                learningFile.Close();
            }
        }
        /// <summary>
        /// Compares 2 arrays (identical in size) of floats.
        /// 
        /// If two floats at the same position in both arrays differ
        /// then the difference is added to the sum.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private float compareFloatArrays(float[] a, float[] b)
        {
            float sum = (float)0;
            if (a.Length == b.Length)
            {
                for (int i = 0; i < a.Length; i++)
                {
                    if (a[i] > b[i])
                    {
                        sum += (a[i] - b[i]);
                    }
                    else if (b[i] > a[i])
                    {
                        sum += (b[i] - a[i]);
                    }
                }
            }
            return sum;
        }
        private int characterToIndex(String s)
        {
            int arrayIndex = 0;
            int asciiValue = Encoding.ASCII.GetBytes(s)[0];
            if ((asciiValue < 58) && (asciiValue > 47)) //0-9
            {
                arrayIndex = asciiValue - 48;
            }
            if ((asciiValue < 91) && (asciiValue > 64)) //A-Z
            {
                arrayIndex = (asciiValue - 65) + 10;
            }
            return arrayIndex;
        }

        /// <summary>
        /// Executed when the OCR button is clicked. 
        /// Matches the loaded character image to a set of images.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonOCR_Click(object sender, EventArgs e)
        {
            ocrCandidatesTextBox.Text = null;
            //create sorted dictionary for adding the compared characters
            //sorted by float key, lower value == higher probability
            SortedDictionary<float, string> OCRresults = new SortedDictionary<float, string>();
            double[] OCRresultsPercentages = new double[36];
            
            //learned characters are stored in the text file in float[40], have to concat arrays:
            float[] normalizedCombined = new float[normalizedColumns.Length+normalizedRows.Length];
            //first half of the float[40] are the columns
            for (int i = 0; i < normalizedColumns.Length; i++)
            {
                normalizedCombined[i] = normalizedColumns[i];
            }
            //second half of the float[40] are the rows
            for (int i = 0; i < normalizedRows.Length; i++)
            {
                normalizedCombined[i + normalizedColumns.Length] = normalizedRows[i];
            }
            try
             {  //read a text file from the same directory as where the program itself resides
                System.IO.StreamReader learningFile = new System.IO.StreamReader(@"learned.txt");
                while (true)
                {
                    String s = learningFile.ReadLine();
                    if (s == null) //end of text file reached
                    {
                        break;
                    }
                    else
                    {   
                        //get the character and values from the file line by splitting:
                        String character = s.Split(';')[0];
                        String normalizedValuesString = s.Split(';')[1];
                        String[] normalizedValuesStringArray = normalizedValuesString.Split(':');
                        //-1 because there is one split too many at the end of the string
                        float[] normalizedFloats = new float[normalizedValuesStringArray.Length - 1];
                        //cast the found values to the newly created array
                        for (int i = 0; i < normalizedFloats.Length; i++)
                        {
                            try
                            {
                                normalizedFloats[i] = float.Parse(normalizedValuesStringArray[i]);
                            }
                            catch (FormatException)
                            {
                                break;
                            }
                        }
                        try
                        {
                            //compare currently loaded image to all the learned characters
                            OCRresults.Add(compareFloatArrays(normalizedFloats, normalizedCombined), character);
                            OCRresultsPercentages[characterToIndex(character)] = compareFloatArrays(normalizedFloats, normalizedCombined);
                        }
                        catch (ArgumentException) { }
                    }
                }
                //output all values sorted by key
                foreach (KeyValuePair<float, string> p in OCRresults)
                {
                    ocrCandidatesTextBox.AppendText(p.Value + " " + p.Key + "\n");
                }
                learningFile.Close();
                double arrayMax = 0;
                foreach (double d in OCRresultsPercentages)
                {
                    if (d > arrayMax)
                    {
                        arrayMax = d;
                    }
                    //debugtextbox.AppendText(d + "\n");
                }
                debugtextbox.Clear();
                debugtextbox.AppendText("max " + arrayMax + "\n");
                for (int i = 0; i < OCRresultsPercentages.Length; i++)
                {
                    if (OCRresultsPercentages[i] > 0)
                    {  
                        OCRresultsPercentages[i] = (100 - (100 * (OCRresultsPercentages[i] / arrayMax)));
                        debugtextbox.AppendText(indexToCharacter(i) + " " + OCRresultsPercentages[i] + "\n");
                    }
                }
            }
            catch (System.IO.FileNotFoundException) {  }
        }
        private char indexToCharacter(int asciiValue)
        {
            if (asciiValue < 10) //0-9
            {
                return (char)(asciiValue + 48);
            }
            else if (asciiValue > 9) //A-Z
            {
                return (char)(asciiValue + 55);
            }
            return '!';
        }

    }
}
